/*
 * Copyright (c) 2018, apexes.net. All rights reserved.
 *
 *         http://www.apexes.net
 *
 */
package net.apexes.commons.querydsl;

import com.querydsl.core.types.Expression;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.Path;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.sql.SQLQuery;
import com.querydsl.sql.SQLQueryFactory;
import com.querydsl.sql.dml.SQLInsertClause;
import net.apexes.commons.lang.Checks;
import net.apexes.commons.querydsl.sql.TablePathBase;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

/**
 * @param <E> 实体类类型
 * @param <ID> PK类型
 * @author <a href="mailto:hedyn@foxmail.com">HeDYn</a>
 */
public class QuerydslDao<E, ID extends Serializable> implements Dao<E, ID> {

    private final SQLQueryFactory factory;
    private final TablePathBase<E> qvar;
    private final QuerydslHelper<E, ID> helper;

    public QuerydslDao(SQLQueryFactory factory, TablePathBase<E> qvar) {
        Checks.verifyNotNull(factory, "factory");
        Checks.verifyNotNull(qvar, "qvar");
        this.factory = factory;
        this.qvar = qvar;
        this.helper = new QuerydslHelper<>(qvar);
    }

    public SQLQueryFactory getFactory() {
        return factory;
    }

    protected QuerydslHelper<E, ID> getQuerydslHelper() {
        return helper;
    }

    @Override
    public SelectExecuter<E, ID> select() {
        return new SelectExecuterImpl();
    }

    @Override
    public SelectExecuter<E, ID> select(Path<?>... columns) {
        if (columns.length == 0) {
            return new SelectExecuterImpl();
        }
        return new SelectExecuterImpl(IncludeColumns.of(columns));
    }

    @Override
    public SelectExecuter<E, ID> selectExclude(Path<?>... columns) {
        return new SelectExecuterImpl(ExcludeColumns.of(columns));
    }

    @Override
    public List<E> find(BooleanExpression condition, OrderSpecifier<?>... orders) {
        return find(condition, (ExcludeColumns) null, orders);
    }

    @Override
    public List<E> find(BooleanExpression condition, Paging paging, OrderSpecifier<?>... orders) {
        return find(condition, (ExcludeColumns) null, paging, orders);
    }

    @Override
    public List<E> find(BooleanExpression condition, ExcludeColumns excludeColumns,
                          OrderSpecifier<?>... orders) {
        return find(condition, excludeColumns, null, null, orders);
    }

    @Override
    public List<E> find(BooleanExpression condition, ExcludeColumns excludeColumns, Paging paging,
                          OrderSpecifier<?>... orders) {
        return find(condition, excludeColumns, null, paging, orders);
    }

    @Override
    public List<E> find(BooleanExpression condition, ExcludeColumns excludeColumns, GroupBy groupBy,
                          OrderSpecifier<?>... orders) {
        return find(condition, excludeColumns, groupBy, null, orders);
    }

    @Override
    public List<E> find(BooleanExpression condition, ExcludeColumns excludeColumns, GroupBy groupBy,
                          Paging paging, OrderSpecifier<?>... orders) {
        Checks.verifyNotNull(condition, "condition");
        return fetch(condition, excludeColumns, paging, groupBy, orders);
    }

    @Override
    public List<E> find(BooleanExpression condition, IncludeColumns includeColumns,
                          OrderSpecifier<?>... orders) {
        return find(condition, includeColumns, null, null, orders);
    }

    @Override
    public List<E> find(BooleanExpression condition, IncludeColumns includeColumns, Paging paging,
                          OrderSpecifier<?>... orders) {
        return find(condition, includeColumns, null, paging, orders);
    }

    @Override
    public List<E> find(BooleanExpression condition, IncludeColumns includeColumns, GroupBy groupBy,
                          OrderSpecifier<?>... orders) {
        return find(condition, includeColumns, groupBy, null, orders);
    }

    @Override
    public List<E> find(BooleanExpression condition, IncludeColumns includeColumns, GroupBy groupBy,
                          Paging paging, OrderSpecifier<?>... orders) {
        Checks.verifyNotNull(condition, "condition");
        return fetch(condition, includeColumns, paging, groupBy, orders);
    }

    @Override
    public List<E> findAll(OrderSpecifier<?>... orders) {
        return fetch(factory.select(qvar).from(qvar).orderBy(orders));
    }

    @Override
    public List<E> findAll(Paging paging, OrderSpecifier<?>... orders) {
        return findAll((IncludeColumns) null, null, orders);
    }

    @Override
    public List<E> findAll(IncludeColumns includeColumns, OrderSpecifier<?>... orders) {
        return findAll(includeColumns, null, orders);
    }

    @Override
    public List<E> findAll(IncludeColumns includeColumns, Paging paging, OrderSpecifier<?>... orders) {
        return fetch(null, includeColumns, paging, null, orders);
    }

    @Override
    public List<E> findAll(ExcludeColumns excludeColumns, OrderSpecifier<?>... orders) {
        return findAll(excludeColumns, null, orders);
    }

    @Override
    public List<E> findAll(ExcludeColumns excludeColumns, Paging paging, OrderSpecifier<?>... orders) {
        return fetch(null, excludeColumns, paging, null, orders);
    }

    @Override
    public E findByPk(ID pk) {
        Checks.verifyNotNull(pk, "pk");
        return findOne(pkValueEqExpr(pk));
    }

    @Override
    public E findByPk(ID pk, IncludeColumns includeColumns) {
        Checks.verifyNotNull(pk, "pk");
        return findOne(pkValueEqExpr(pk), includeColumns);
    }

    @Override
    public E findByPk(ID pk, ExcludeColumns excludeColumns) {
        Checks.verifyNotNull(pk, "pk");
        return findOne(pkValueEqExpr(pk), excludeColumns);
    }

    @Override
    public E findOne(BooleanExpression condition) {
        Checks.verifyNotNull(condition, "condition");
        return fetchOne(qvar, condition, null);
    }

    @Override
    public E findOne(BooleanExpression condition, IncludeColumns includeColumns) {
        return findOne(condition, includeColumns, null);
    }

    @Override
    public E findOne(BooleanExpression condition, IncludeColumns includeColumns, GroupBy groupBy) {
        Checks.verifyNotNull(condition, "condition");
        Expression<E> projection = qvar;
        if (includeColumns != null) {
            projection = listColumns(includeColumns);
        }
        return fetchOne(projection, condition, groupBy);
    }

    @Override
    public E findOne(BooleanExpression condition, ExcludeColumns excludeColumns) {
        return findOne(condition, excludeColumns, null);
    }

    @Override
    public E findOne(BooleanExpression condition, ExcludeColumns excludeColumns, GroupBy groupBy) {
        Checks.verifyNotNull(condition, "condition");
        Expression<E> projection = qvar;
        if (excludeColumns != null) {
            projection = listColumns(excludeColumns);
        }
        return fetchOne(projection, condition, groupBy);
    }

    @Override
    public E findFirst(BooleanExpression condition, OrderSpecifier<?>... orders) {
        return findFirst(condition, (GroupBy) null, orders);
    }

    @Override
    public E findFirst(BooleanExpression condition, GroupBy groupBy, OrderSpecifier<?>... orders) {
        return fetchFirst(qvar, condition, groupBy, orders);
    }

    @Override
    public E findFirst(BooleanExpression condition, IncludeColumns includeColumns, OrderSpecifier<?>... orders) {
        return findFirst(condition, includeColumns, null, orders);
    }

    @Override
    public E findFirst(BooleanExpression condition, IncludeColumns includeColumns, GroupBy groupBy,
                         OrderSpecifier<?>... orders) {
        Expression<E> projection = qvar;
        if (includeColumns != null) {
            projection = listColumns(includeColumns);
        }
        return fetchFirst(projection, condition, groupBy, orders);
    }

    @Override
    public E findFirst(BooleanExpression condition, ExcludeColumns excludeColumns, OrderSpecifier<?>... orders) {
        return findFirst(condition, excludeColumns, null, orders);
    }

    @Override
    public E findFirst(BooleanExpression condition, ExcludeColumns excludeColumns, GroupBy groupBy,
                         OrderSpecifier<?>... orders) {
        Expression<E> projection = qvar;
        if (excludeColumns != null) {
            projection = listColumns(excludeColumns);
        }
        return fetchFirst(projection, condition, groupBy, orders);
    }

    @Override
    public boolean exist() {
        return fetchFirst(factory.selectOne().from(qvar)) != null;
    }

    @Override
    public boolean exist(BooleanExpression condition) {
        Checks.verifyNotNull(condition, "condition");
        return fetchFirst(factory.selectOne().from(qvar).where(condition)) != null;
    }

    @Override
    public boolean notExist() {
        return !exist();
    }

    @Override
    public boolean notExist(BooleanExpression condition) {
        return !exist(condition);
    }

    @Override
    public boolean existByPk(ID pk) {
        Checks.verifyNotNull(pk, "pk");
        BooleanExpression pkCondition = pkValueEqExpr(pk);
        return fetchFirst(factory.selectOne().from(qvar).where(pkCondition)) != null;
    }

    @Override
    public boolean notExistByPk(ID pk) {
        return !existByPk(pk);
    }

    @Override
    public long count() {
        return fetchCount(factory.selectFrom(qvar));
    }

    @Override
    public long count(BooleanExpression condition) {
        Checks.verifyNotNull(condition, "condition");
        return fetchCount(factory.selectFrom(qvar).where(condition));
    }

    @Override
    public long insert(E entity) {
        Checks.verifyNotNull(entity, "entity");
        return executeInsert(insertPathValuePair(entity));
    }

    @Override
    public long insertBatch(List<E> list) {
        Checks.verifyNotNull(list, "list");
        List<PathValuePair<E, ID>> pairList = new ArrayList<>();
        for (E entity : list) {
            pairList.add(insertPathValuePair(entity));
        }
        return executeInsertBatch(pairList);
    }

    @Override
    public UpdateExecuter<E, ID> update() {
        return new UpdateExecuterImpl();
    }

    @Override
    public UpdateExecuter<E, ID> update(BooleanExpression where) {
        return new UpdateExecuterImpl().where(where);
    }

    @Override
    public UpdateExecuter<E, ID> update(ID pk) {
        return new UpdateExecuterImpl().where(pk);
    }

    @Override
    public long update(BooleanExpression condition, E entity, Path<?>... updatePaths) {
        Checks.verifyNotNull(condition, "condition");
        Checks.verifyNotNull(entity, "entity");
        Checks.verifyNotEmpty(updatePaths, "updatePaths");
        return updateImpl(condition, helper.createPathValuePair(entity, updatePaths));
    }

    @Override
    public long update(BooleanExpression condition, E entity, IncludeColumns includeColumns) {
        Checks.verifyNotNull(includeColumns, "includeColumns");
        return update(condition, entity, includeColumns.getIncludeColumns());
    }

    @Override
    public long update(BooleanExpression condition, E entity, ExcludeColumns excludeColumns) {
        Checks.verifyNotNull(condition, "condition");
        Checks.verifyNotNull(entity, "entity");
        Checks.verifyNotNull(excludeColumns, "excludeColumns");
        return updateImpl(condition, helper.createPathValuePair(entity, excludeColumns));
    }

    @Override
    public boolean updateByPk(ID pk, E entity) {
        return updateByPk(pk, entity, helper.withoutPkColumns());
    }

    @Override
    public boolean updateByPk(ID pk, E entity, Path<?>... updatePaths) {
        Checks.verifyNotNull(pk, "pk");
        Checks.verifyNotNull(entity, "entity");
        Checks.verifyNotEmpty(updatePaths, "updatePaths");
        for (Path<?> column : updatePaths) {
            if (helper.isPkColumn(column)) {
                throw new RuntimeException("doest't support update pk column itself, column:" + column);
            }
        }
        BooleanExpression pkCondition = pkValueEqExpr(pk);
        return updateOneImpl(pkCondition, helper.createPathValuePair(entity, updatePaths));
    }

    @Override
    public boolean updateByPk(ID pk, E entity, IncludeColumns includeColumns) {
        Checks.verifyNotNull(includeColumns, "includeColumns");
        return updateByPk(pk, entity, includeColumns.getIncludeColumns());
    }

    @Override
    public boolean updateByPk(ID pk, E entity, ExcludeColumns excludeColumns) {
        Checks.verifyNotNull(pk, "pk");
        Checks.verifyNotNull(entity, "entity");
        Checks.verifyNotNull(excludeColumns, "excludeColumns");
        ExcludeColumns excludeCols = new ExcludeColumns();
        boolean excludePk = false;
        for (Path<?> column : excludeColumns.getExcludeColumns()) {
            excludeCols.add(column);
            if (helper.isPkColumn(column)) {
                excludePk = true;
            }
        }
        if (!excludePk) {
            excludeCols.add(helper.pkPath());
        }
        BooleanExpression pkCondition = pkValueEqExpr(pk);
        return updateOneImpl(pkCondition, helper.createPathValuePair(entity, excludeCols));
    }

    @Override
    public boolean updateOne(BooleanExpression condition, E entity, Path<?>... updatePaths) {
        Checks.verifyNotNull(condition, "condition");
        Checks.verifyNotNull(entity, "entity");
        Checks.verifyNotNull(updatePaths, "updatePaths");
        return updateOneImpl(condition, helper.createPathValuePair(entity, updatePaths));
    }

    @Override
    public boolean updateOne(BooleanExpression condition, E entity, IncludeColumns includeColumns) {
        Checks.verifyNotNull(includeColumns, "includeColumns");
        return updateOne(condition, entity, includeColumns.getIncludeColumns());
    }

    @Override
    public boolean updateOne(BooleanExpression condition, E entity, ExcludeColumns excludeColumns) {
        Checks.verifyNotNull(condition, "condition");
        Checks.verifyNotNull(entity, "entity");
        Checks.verifyNotNull(excludeColumns, "excludeColumns");
        return updateOneImpl(condition, helper.createPathValuePair(entity, excludeColumns));
    }

    @Override
    public long updateAll(E entity, Path<?>... updatePaths) {
        Checks.verifyNotNull(entity, "entity");
        Checks.verifyNotEmpty(updatePaths, "updatePaths");
        return updateImpl(null, helper.createPathValuePair(entity, updatePaths));
    }

    @Override
    public long updateAll(E entity, IncludeColumns includeColumns) {
        Checks.verifyNotNull(entity, "entity");
        Checks.verifyNotNull(includeColumns, "includeColumns");
        return updateImpl(null, helper.createPathValuePair(entity, includeColumns));
    }

    @Override
    public long updateAll(E entity, ExcludeColumns excludeColumns) {
        Checks.verifyNotNull(entity, "entity");
        Checks.verifyNotNull(excludeColumns, "excludeColumns");
        return updateImpl(null, helper.createPathValuePair(entity, excludeColumns));
    }

    @Override
    public boolean deleteByPk(ID pk) {
        Checks.verifyNotNull(pk, "pk");
        BooleanExpression pkCondition = pkValueEqExpr(pk);
        return deleteOneImpl(pkCondition);
    }

    @Override
    public boolean deleteOne(BooleanExpression condition) {
        Checks.verifyNotNull(condition, "condition");
        return deleteOneImpl(condition);
    }

    @Override
    public long delete(BooleanExpression condition) {
        Checks.verifyNotNull(condition, "condition");
        return deleteImpl(condition);
    }

    private BooleanExpression pkValueEqExpr(ID pk) {
        return helper.pkValueEqExpr(pk);
    }

    private SQLQuery<E> groupBy(SQLQuery<E> sqlQuery, GroupBy groupBy) {
        if (groupBy != null) {
            sqlQuery = sqlQuery.groupBy(groupBy.getColumns());
            if (groupBy.getHavings() != null && groupBy.getColumns().length > 0) {
                sqlQuery = sqlQuery.having(groupBy.getHavings());
            }
        }
        return sqlQuery;
    }

    private IncludeColumns addOrderByColumns(IncludeColumns includeColumns, OrderSpecifier<?>... orders) {
        if (includeColumns != null && orders != null) {
            for (OrderSpecifier<?> order : orders) {
                includeColumns.add((Path<?>) order.getTarget());
            }
        }
        return includeColumns;
    }

    private ExcludeColumns addOrderByColumns(ExcludeColumns excludeColumns, OrderSpecifier<?>... orders) {
        if (excludeColumns != null && orders != null) {
            for (OrderSpecifier<?> order : orders) {
                excludeColumns.remove((Path<?>) order.getTarget());
            }
        }
        return excludeColumns;
    }

    private List<E> fetch(BooleanExpression condition, ExcludeColumns excludeColumns, Paging paging,
                          GroupBy groupBy, OrderSpecifier<?>... orders) {
        excludeColumns = addOrderByColumns(excludeColumns, orders);
        Expression<E> projection = listColumns(excludeColumns);
        return fetch(projection, condition, paging, groupBy, orders);
    }

    private List<E> fetch(BooleanExpression condition, IncludeColumns includeColumns, Paging paging,
                          GroupBy groupBy, OrderSpecifier<?>... orders) {
        includeColumns = addOrderByColumns(includeColumns, orders);
        Expression<E> projection = listColumns(includeColumns);
        return fetch(projection, condition, paging, groupBy, orders);
    }

    private List<E> fetch(Expression<E> projection, BooleanExpression condition, Paging paging, GroupBy groupBy,
                          OrderSpecifier<?>... orders) {
        SQLQuery<E> sqlQuery = selectQuery(projection, condition, paging, groupBy, orders);
        return fetch(sqlQuery);
    }

    private E fetchOne(Expression<E> projection, BooleanExpression condition, GroupBy groupBy) {
        SQLQuery<E> sqlQuery = selectQuery(projection, condition, null, groupBy);
        return fetchOne(sqlQuery);
    }

    private E fetchFirst(Expression<E> projection, BooleanExpression condition, GroupBy groupBy,
                         OrderSpecifier<?>... orders) {
        SQLQuery<E> sqlQuery = selectQuery(projection, condition, null, groupBy, orders);
        return fetchFirst(sqlQuery);
    }

    private SQLQuery<E> selectQuery(Expression<E> projection, BooleanExpression condition, Paging paging,
                                    GroupBy groupBy, OrderSpecifier<?>... orders) {
        SQLQuery<E> sqlQuery = factory.select(projection).from(qvar);
        if (orders != null) {
            sqlQuery = sqlQuery.orderBy(orders);
        }
        if (condition != null) {
            sqlQuery = sqlQuery.where(condition);
        }
        if (paging != null) {
            sqlQuery = sqlQuery.offset(paging.getOffset());
            if (paging.getLimit() != null) {
                sqlQuery = sqlQuery.limit(paging.getLimit());
            }
        }
        if (groupBy != null) {
            sqlQuery = groupBy(sqlQuery, groupBy);
        }
        return sqlQuery;
    }

    protected <I> long fetchCount(SQLQuery<I> sqlQuery) {
        return filerSelectQuery(sqlQuery).fetchCount();
    }

    protected <I> I fetchOne(SQLQuery<I> sqlQuery) {
        return filerSelectQuery(sqlQuery).fetchOne();
    }

    protected <I> I fetchFirst(SQLQuery<I> sqlQuery) {
        return filerSelectQuery(sqlQuery).fetchFirst();
    }

    protected <I> List<I> fetch(SQLQuery<I> sqlQuery) {
        return filerSelectQuery(sqlQuery).fetch();
    }

    protected <I> SQLQuery<I> filerSelectQuery(SQLQuery<I> sqlQuery) {
        return sqlQuery;
    }

    private Expression<E> listColumns(IncludeColumns includeColumns) {
        if (includeColumns == null) {
            return qvar;
        }
        if (!includeColumns.hasColumns()) {
            return qvar;
        }
        return helper.createtProjection(includeColumns);
    }

    private Expression<E> listColumns(ExcludeColumns excludeColumns) {
        if (excludeColumns == null) {
            return qvar;
        }
        if (!excludeColumns.hasColumns()) {
            return qvar;
        }
        return helper.createtProjection(excludeColumns);
    }

    private PathValuePair<E, ID> insertPathValuePair(E entity) {
        Path<?>[] paths = helper.allPaths();
        Object[] values = helper.entityValues(entity);
        // 过滤掉有设置默认值但值为null的
        List<Path<?>> pathList = new ArrayList<>();
        List<Object> valueList = new ArrayList<>();
        for (int i = 0; i < paths.length; i++) {
            if (values[i] == null && qvar.hasDefaultValue(paths[i])) {
                continue;
            }
            pathList.add(paths[i]);
            valueList.add(values[i]);
        }
        return insertPathValuePair(pathList, valueList);
    }

    protected PathValuePair<E, ID> insertPathValuePair(List<Path<?>> pathList, List<Object> valueList) {
        return new PathValuePair<>(pathList, valueList);
    }

    protected long executeInsert(PathValuePair<E, ID> pair) {
        return factory.insert(qvar)
                .columns(pair.getPaths()).values(pair.getValues())
                .execute();
    }

    protected long executeInsertBatch(List<PathValuePair<E, ID>> pairList) {
        SQLInsertClause clause = factory.insert(qvar);
        for (PathValuePair<E, ID> pair : pairList) {
            clause.columns(pair.getPaths()).values(pair.getValues()).addBatch();
        }
        return clause.execute();
    }

    private boolean updateOneImpl(BooleanExpression condition, PathValuePair<E, ID> pair) {
        return updateOneImpl(condition, pair.getPathsAsList(), pair.getValuesAsList());
    }

    private boolean updateOneImpl(BooleanExpression condition, List<Path<?>> paths, List<?> values) {
        return updateImpl(condition, paths, values) == 1;
    }

    private long updateImpl(BooleanExpression condition, PathValuePair<E, ID> pair) {
        return updateImpl(condition, pair.getPathsAsList(), pair.getValuesAsList());
    }

    private long updateImpl(BooleanExpression condition, List<Path<?>> paths, List<?> values) {
        for (Path<?> path : paths) {
            if (!helper.isEntityColumn(path)) {
                throw new RuntimeException("not found column. " + path);
            }
            if (helper.isPkColumn(path)) {
                throw new IllegalArgumentException("update doesn't support update pk column. " + path);
            }
        }
        return executeUpdate(condition, updatePathValuePair(paths, values));
    }

    protected PathValuePair<E, ID> updatePathValuePair(List<Path<?>> pathList, List<?> valueList) {
        return new PathValuePair<>(pathList, valueList);
    }

    protected long executeUpdate(BooleanExpression condition, PathValuePair<E, ID> pair) {
        return factory.update(qvar)
                .set(pair.getPathsAsList(), pair.getValuesAsList())
                .where(condition)
                .execute();
    }

    private boolean deleteOneImpl(BooleanExpression condition) {
        return executeDelete(condition) == 1;
    }

    private long deleteImpl(BooleanExpression condition) {
        return executeDelete(condition);
    }

    protected long executeDelete(BooleanExpression condition) {
        return factory.delete(qvar).where(condition).execute();
    }

    /**
     * @author <a href="mailto:hedyn@foxmail.com">HeDYn</a>
     */
    private class SelectExecuterImpl implements SelectExecuter<E, ID> {

        private IncludeColumns includeColumns;
        private ExcludeColumns excludeColumns;
        private BooleanExpression where;
        private OrderSpecifier<?>[] orderBys;
        private Path<?>[] groupByColumns;
        private Predicate[] havings;
        private Long offset;
        private Long limit;

        private SelectExecuterImpl() {
        }

        private SelectExecuterImpl(IncludeColumns includeColumns) {
            this.includeColumns = includeColumns;
            this.excludeColumns = null;
        }

        private SelectExecuterImpl(ExcludeColumns excludeColumns) {
            this.includeColumns = null;
            this.excludeColumns = excludeColumns;
        }

        @Override
        public SelectExecuter<E, ID> where(BooleanExpression where) {
            this.where = where;
            return this;
        }

        @Override
        public SelectExecuter<E, ID> where(ID pk) {
            this.where = pkValueEqExpr(pk);
            return this;
        }

        @Override
        public SelectExecuter<E, ID> orderBy(OrderSpecifier<?>... orderBys) {
            this.orderBys = orderBys;
            return this;
        }

        @Override
        public SelectExecuter<E, ID> groupBy(Path<?>... columns) {
            this.groupByColumns = columns;
            return this;
        }

        @Override
        public SelectExecuter<E, ID> having(Predicate... havings) {
            this.havings = havings;
            return this;
        }

        @Override
        public SelectExecuter<E, ID> paging(long offset, long limit) {
            this.offset = offset;
            this.limit = limit;
            return this;
        }

        @Override
        public SelectExecuter<E, ID> offset(long offset) {
            this.offset = offset;
            return this;
        }

        @Override
        public SelectExecuter<E, ID> limit(long limit) {
            this.limit = limit;
            return this;
        }

        @Override
        public List<E> fetch() {
            if (excludeColumns == null) {
                return QuerydslDao.this.fetch(where, includeColumns, createPaging(), createGroupBy(), orderBys);
            }
            return QuerydslDao.this.fetch(where, excludeColumns, createPaging(), createGroupBy(), orderBys);
        }

        @Override
        public E fetchFirst() {
            Expression<E> projection = qvar;
            if (excludeColumns != null) {
                projection = listColumns(excludeColumns);
            }
            return QuerydslDao.this.fetchFirst(projection, where, createGroupBy(), orderBys);
        }

        @Override
        public E fetchOne() {
            Expression<E> projection = qvar;
            if (excludeColumns != null) {
                projection = listColumns(excludeColumns);
            }
            return QuerydslDao.this.fetchOne(projection, where, createGroupBy());
        }

        private Paging createPaging() {
            if (limit == null && offset == null) {
                return null;
            }
            if (offset == null) {
                return Paging.limit(limit);
            } else if (limit == null) {
                return Paging.offset(offset);
            }
            return Paging.of(offset, limit);
        }

        private GroupBy createGroupBy() {
            if (groupByColumns == null) {
                return null;
            }
            return GroupBy.of(groupByColumns).having(havings);
        }
    }

    /**
     * @author <a href="mailto:hedyn@foxmail.com">HeDYn</a>
     */
    private class UpdateExecuterImpl implements UpdateExecuter<E, ID> {

        private BooleanExpression where;

        private UpdateExecuterImpl() {
        }

        @Override
        public UpdateExecuter<E, ID> where(BooleanExpression where) {
            this.where = where;
            return this;
        }

        @Override
        public UpdateExecuter<E, ID> where(ID pk) {
            this.where = pkValueEqExpr(pk);
            return this;
        }

        @Override
        public UpdateEntityExecuter<E, ID> set(E entity) {
            return new UpdateEntityExecuterImpl(entity, where);
        }

        @Override
        public <T> UpdateValueExecuter<E, ID> set(Path<T> column, T value) {
            return new UpdateValueExecuterImpl(where).set(column, value);
        }

        @Override
        public <T> UpdateValueExecuter<E, ID> set(Path<T> column, Expression<? extends T> expression) {
            return new UpdateValueExecuterImpl(where).set(column, expression);
        }

        @Override
        public <T> UpdateValueExecuter<E, ID> setNull(Path<T> column) {
            return new UpdateValueExecuterImpl(where).setNull(column);
        }
    }

    /**
     * @author <a href="mailto:hedyn@foxmail.com">HeDYn</a>
     */
    private class UpdateEntityExecuterImpl implements UpdateEntityExecuter<E, ID> {

        private final E entity;
        private IncludeColumns includeColumns;
        private ExcludeColumns excludeColumns;
        private BooleanExpression where;

        private UpdateEntityExecuterImpl(E entity, BooleanExpression where) {
            this.entity = entity;
            this.where = where;
        }

        @Override
        public UpdateEntityExecuter<E, ID> include(Path<?>... columns) {
            this.includeColumns = IncludeColumns.of(columns);
            this.excludeColumns = null;
            return this;
        }

        @Override
        public UpdateEntityExecuter<E, ID> exclude(Path<?>... columns) {
            this.includeColumns = null;
            this.excludeColumns = ExcludeColumns.of(columns);
            return this;
        }

        @Override
        public UpdateEntityExecuter<E, ID> where(BooleanExpression where) {
            this.where = where;
            return this;
        }

        @Override
        public UpdateEntityExecuter<E, ID> where(ID pk) {
            this.where = pkValueEqExpr(pk);
            return this;
        }

        @Override
        public long execute() {
            if (excludeColumns != null) {
                return update(where, entity, excludeColumns);
            }
            if (includeColumns != null) {
                return update(where, entity, includeColumns);
            }
            return update(where, entity, helper.withoutPkColumns());
        }

        @Override
        public boolean executeOne() {
            if (excludeColumns != null) {
                return updateOne(where, entity, excludeColumns);
            }
            if (includeColumns != null) {
                return updateOne(where, entity, includeColumns);
            }
            return updateOne(where, entity, helper.withoutPkColumns());
        }
    }

    /**
     * @author <a href="mailto:hedyn@foxmail.com">HeDYn</a>
     */
    private class UpdateValueExecuterImpl implements UpdateValueExecuter<E, ID> {

        private final List<Path<?>> paths;
        private final List<Object> values;
        private BooleanExpression where;

        private UpdateValueExecuterImpl(BooleanExpression where) {
            this.where = where;
            this.paths = new ArrayList<>();
            this.values = new ArrayList<>();
        }

        @Override
        public <T> UpdateValueExecuter<E, ID> set(Path<T> column, T value) {
            paths.add(column);
            values.add(value);
            return this;
        }

        @Override
        public <T> UpdateValueExecuter<E, ID> set(Path<T> column, Expression<? extends T> expression) {
            paths.add(column);
            values.add(expression);
            return this;
        }

        @Override
        public <T> UpdateValueExecuter<E, ID> setNull(Path<T> column) {
            paths.add(column);
            values.add(null);
            return this;
        }

        @Override
        public UpdateValueExecuter<E, ID> where(BooleanExpression where) {
            this.where = where;
            return this;
        }

        @Override
        public UpdateValueExecuter<E, ID> where(ID pk) {
            this.where = pkValueEqExpr(pk);
            return this;
        }

        @Override
        public long execute() {
            Checks.verifyNotNull(where, "where");
            return updateImpl(where, paths, values);
        }

        @Override
        public boolean executeOne() {
            Checks.verifyNotNull(where, "where");
            return updateOneImpl(where, paths, values);
        }
    }

}
